<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Dojo
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: BuildLayer.php 18951 2009-11-12 16:26:19Z alexander $
 */

/**
 * Dojo module layer and custom build profile generation support
 *
 * @package    Zend_Dojo
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Dojo_BuildLayer
{
	/**
	 * Flag: whether or not to consume JS aggregated in the dojo() view
	 * helper when generate the module layer contents
	 * @var bool
	 */
	protected $_consumeJavascript = false;

	/**
	 * Flag: whether or not to consume dojo.addOnLoad events registered
	 * with the dojo() view helper when generating the module layer file
	 * contents
	 * @var bool
	 */
	protected $_consumeOnLoad = false;

	/**
	 * Dojo view helper reference
	 * @var Zend_Dojo_View_Helper_Dojo_Container
	 */
	protected $_dojo;

	/**
	 * Name of the custom layer to generate
	 * @var string
	 */
	protected $_layerName;

	/**
	 * Path to the custom layer script relative to dojo.js (used when
	 * creating the build profile)
	 * @var string
	 */
	protected $_layerScriptPath;

	/**
	 * Build profile options
	 * @var array
	 */
	protected $_profileOptions = array(
        'action'        => 'release',
        'optimize'      => 'shrinksafe',
        'layerOptimize' => 'shrinksafe',
        'copyTests'     => false,
        'loader'        => 'default',
        'cssOptimize'   => 'comments',
	);

	/**
	 * Associative array of module/path pairs for the build profile
	 * @var array
	 */
	protected $_profilePrefixes = array();

	/**
	 * Zend_View reference
	 * @var Zend_View_Interface
	 */
	protected $_view;

	/**
	 * Constructor
	 *
	 * @param  array|Zend_Config $options
	 * @return void
	 * @throws Zend_Dojo_Exception for invalid option argument
	 */
	public function __construct($options = null)
	{
		if (null !== $options) {
			if ($options instanceof Zend_Config) {
				$options = $options->toArray();
			} elseif (!is_array($options)) {
				require_once 'Zend/Dojo/Exception.php';
				throw new Zend_Dojo_Exception('Invalid options provided to constructor');
			}
			$this->setOptions($options);
		}
	}

	/**
	 * Set options
	 *
	 * Proxies to any setter that matches an option key.
	 *
	 * @param  array $options
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setOptions(array $options)
	{
		$methods = get_class_methods($this);
		foreach ($options as $key => $value) {
			$method = 'set' . ucfirst($key);
			if (in_array($method, $methods)) {
				$this->$method($value);
			}
		}
		return $this;
	}

	/**
	 * Set View object
	 *
	 * @param  Zend_View_Interface $view
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setView(Zend_View_Interface $view)
	{
		$this->_view = $view;
		return $this;
	}

	/**
	 * Retrieve view object
	 *
	 * @return Zend_View_Interface|null
	 */
	public function getView()
	{
		return $this->_view;
	}

	/**
	 * Set dojo() view helper instance
	 *
	 * @param  Zend_Dojo_View_Helper_Dojo_Container $helper
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setDojoHelper(Zend_Dojo_View_Helper_Dojo_Container $helper)
	{
		$this->_dojo = $helper;
		return $this;
	}

	/**
	 * Retrieve dojo() view helper instance
	 *
	 * Will retrieve it from the view object if not registered.
	 *
	 * @return Zend_Dojo_View_Helper_Dojo_Container
	 * @throws Zend_Dojo_Exception if not registered and no view object found
	 */
	public function getDojoHelper()
	{
		if (null === $this->_dojo) {
			if (null === ($view = $this->getView())) {
				require_once 'Zend/Dojo/Exception.php';
				throw new Zend_Dojo_Exception('View object not registered; cannot retrieve dojo helper');
			}
			$helper = $view->getHelper('dojo');
			$this->setDojoHelper($view->dojo());
		}
		return $this->_dojo;
	}

	/**
	 * Set custom layer name; e.g. "custom.main"
	 *
	 * @param  string $name
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setLayerName($name)
	{
		if (!preg_match('/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i', $name)) {
			require_once 'Zend/Dojo/Exception.php';
			throw new Zend_Dojo_Exception('Invalid layer name provided; must be of form[a-z][a-z0-9_](\.[a-z][a-z0-9_])+');
		}
		$this->_layerName = $name;
		return $this;
	}

	/**
	 * Retrieve custom layer name
	 *
	 * @return string|null
	 */
	public function getLayerName()
	{
		return $this->_layerName;
	}

	/**
	 * Set the path to the custom layer script
	 *
	 * Should be a path relative to dojo.js
	 *
	 * @param  string $path
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setLayerScriptPath($path)
	{
		$this->_layerScriptPath = (string) $path;
		return $this;
	}

	/**
	 * Get custom layer script path
	 *
	 * @return string|null
	 */
	public function getLayerScriptPath()
	{
		return $this->_layerScriptPath;
	}

	/**
	 * Set flag indicating whether or not to consume JS aggregated in dojo()
	 * view helper
	 *
	 * @param  bool $flag
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setConsumeJavascript($flag)
	{
		$this->_consumeJavascript = (bool) $flag;
		return $this;
	}

	/**
	 * Get flag indicating whether or not to consume JS aggregated in dojo()
	 * view helper
	 *
	 * @return bool
	 */
	public function consumeJavascript()
	{
		return $this->_consumeJavascript;
	}

	/**
	 * Set flag indicating whether or not to consume dojo.addOnLoad events
	 * aggregated in dojo() view helper
	 *
	 * @param  bool $flag
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setConsumeOnLoad($flag)
	{
		$this->_consumeOnLoad = (bool) $flag;
		return $this;
	}

	/**
	 * Get flag indicating whether or not to consume dojo.addOnLoad events aggregated in dojo() view helper
	 *
	 * @return bool
	 */
	public function consumeOnLoad()
	{
		return $this->_consumeOnLoad;
	}

	/**
	 * Set many build profile options at once
	 *
	 * @param  array $options
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setProfileOptions(array $options)
	{
		$this->_profileOptions += $options;
		return $this;
	}

	/**
	 * Add many build profile options at once
	 *
	 * @param  array $options
	 * @return Zend_Dojo_BuildLayer
	 */
	public function addProfileOptions(array $options)
	{
		$this->_profileOptions = $this->_profileOptions + $options;
		return $this;
	}

	/**
	 * Add a single build profile option
	 *
	 * @param  string $key
	 * @param  value $value
	 * @return Zend_Dojo_BuildLayer
	 */
	public function addProfileOption($key, $value)
	{
		$this->_profileOptions[(string) $key] = $value;
		return $this;
	}

	/**
	 * Is a given build profile option set?
	 *
	 * @param  string $key
	 * @return bool
	 */
	public function hasProfileOption($key)
	{
		return array_key_exists((string) $key, $this->_profileOptions);
	}

	/**
	 * Retrieve a single build profile option
	 *
	 * Returns null if profile option does not exist.
	 *
	 * @param  string $key
	 * @return mixed
	 */
	public function getProfileOption($key)
	{
		if ($this->hasProfileOption($key)) {
			return $this->_profileOptions[(string) $key];
		}
		return null;
	}

	/**
	 * Get all build profile options
	 *
	 * @return array
	 */
	public function getProfileOptions()
	{
		return $this->_profileOptions;
	}

	/**
	 * Remove a build profile option
	 *
	 * @param  string $name
	 * @return Zend_Dojo_BuildLayer
	 */
	public function removeProfileOption($name)
	{
		if ($this->hasProfileOption($name)) {
			unset($this->_profileOptions[(string) $name]);
		}
		return $this;
	}

	/**
	 * Remove all build profile options
	 *
	 * @return Zend_Dojo_BuildLayer
	 */
	public function clearProfileOptions()
	{
		$this->_profileOptions = array();
		return $this;
	}

	/**
	 * Add a build profile dependency prefix
	 *
	 * If just the prefix is passed, sets path to "../$prefix".
	 *
	 * @param  string $prefix
	 * @param  null|string $path
	 * @return Zend_Dojo_BuildLayer
	 */
	public function addProfilePrefix($prefix, $path = null)
	{
		if (null === $path) {
			$path = '../' . $prefix;
		}
		$this->_profilePrefixes[$prefix] = array($prefix, $path);
		return $this;
	}

	/**
	 * Set multiple dependency prefixes for bulid profile
	 *
	 * @param  array $prefixes
	 * @return Zend_Dojo_BuildLayer
	 */
	public function setProfilePrefixes(array $prefixes)
	{
		foreach ($prefixes as $prefix => $path) {
			$this->addProfilePrefix($prefix, $path);
		}
		return $this;
	}

	/**
	 * Get build profile dependency prefixes
	 *
	 * @return array
	 */
	public function getProfilePrefixes()
	{
		$layerName = $this->getLayerName();
		if (null !== $layerName) {
			$prefix    = $this->_getPrefix($layerName);
			if (!array_key_exists($prefix, $this->_profilePrefixes)) {
				$this->addProfilePrefix($prefix);
			}
		}
		$view = $this->getView();
		if (!empty($view)) {
			$helper = $this->getDojoHelper();
			if ($helper) {
				$modules = $helper->getModules();
				foreach ($modules as $module) {
					$prefix = $this->_getPrefix($module);
					if (!array_key_exists($prefix, $this->_profilePrefixes)) {
						$this->addProfilePrefix($prefix);
					}
				}
			}
		}
		return $this->_profilePrefixes;
	}

	/**
	 * Generate module layer script
	 *
	 * @return string
	 */
	public function generateLayerScript()
	{
		$helper        = $this->getDojoHelper();
		$layerName     = $this->getLayerName();
		$modulePaths   = $helper->getModulePaths();
		$modules       = $helper->getModules();
		$onLoadActions = $helper->getOnLoadActions();
		$javascript    = $helper->getJavascript();

		$content = 'dojo.provide("' . $layerName . '");' . "\n\n(function(){\n";

		foreach ($modulePaths as $module => $path) {
			$content .= sprintf("dojo.registerModulePath(\"%s\", \"%s\");\n", $module, $path);
		}
		foreach ($modules as $module) {
			$content .= sprintf("dojo.require(\"%s\");\n", $module);
		}

		if ($this->consumeOnLoad()) {
			foreach ($helper->getOnLoadActions() as $callback) {
				$content .= sprintf("dojo.addOnLoad(%s);\n", $callback);
			}
		}
		if ($this->consumeJavascript()) {
			$javascript = implode("\n", $helper->getJavascript());
			if (!empty($javascript)) {
				$content .= "\n" . $javascript . "\n";
			}
		}

		$content .= "})();";

		return $content;
	}

	/**
	 * Generate build profile
	 *
	 * @return string
	 */
	public function generateBuildProfile()
	{
		$profileOptions  = $this->getProfileOptions();
		$layerName       = $this->getLayerName();
		$layerScriptPath = $this->getLayerScriptPath();
		$profilePrefixes = $this->getProfilePrefixes();

		if (!array_key_exists('releaseName', $profileOptions)) {
			$profileOptions['releaseName'] = substr($layerName, 0, strpos($layerName, '.'));
		}

		$profile = $profileOptions;
		$profile['layers'] = array(array(
            'name' => $layerScriptPath,
            'layerDependencies' => array(),
            'dependencies' => array($layerName),
		));
		$profile['prefixes'] = array_values($profilePrefixes);

		return 'dependencies = ' . $this->_filterJsonProfileToJavascript($profile) . ';';
	}

	/**
	 * Retrieve module prefix
	 *
	 * @param  string $module
	 * @return void
	 */
	protected function _getPrefix($module)
	{
		$segments  = explode('.', $module, 2);
		return $segments[0];
	}

	/**
	 * Filter a JSON build profile to JavaScript
	 *
	 * @param  string $profile
	 * @return string
	 */
	protected function _filterJsonProfileToJavascript($profile)
	{
		require_once 'Zend/Json.php';
		$profile = Zend_Json::encode($profile);
		$profile = preg_replace('/"([^"]*)":/', '$1:', $profile);
		$profile = preg_replace('/' . preg_quote('\\') . '/', '', $profile);
		return $profile;
	}
}
